package board

import (
	"context"
	"strconv"

	"code.byted.org/bytertc/whiteboard_server/biz/api/pb"
	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/base"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
)

type boardAction struct {
	base.Action
	rawAction *v1.Action
}

func (b *boardAction) GetActionId() int32 {
	return b.rawAction.GetActionId()
}

func (b *boardAction) GetObjId() string {
	return strconv.FormatInt(int64(b.BoardId), 10)
}

func (b *boardAction) ConflictObjects() []string {
	return []string{b.PageId}
}

func (b *boardAction) IsAtomic() bool {
	return true
}

func (b *boardAction) RawActions() []*v1.Action {
	return []*v1.Action{b.rawAction}
}

func (b *boardAction) GetOperator() string {
	return b.Operator
}
func (b *boardAction) Execute(ctx context.Context) error {
	return nil
}
func (b *boardAction) checkConflict(ctx context.Context) error {
	if err := b.Repo.Authenticate(ctx, b); err != nil {
		return err
	}
	return nil
}

func NewAction(ctx context.Context, baseAct base.Action, rawAction *v1.Action) (act action.Action, err error) {
	msg, err := pb.UnmarshalAction(rawAction.Type, rawAction.Data)
	if err != nil {
		return nil, biz_error.DecodeError.Wrap(errors.Wrapf(err, "fail to unmarshal %s", rawAction.Type))
	}
	switch rawAction.Type {
	case v1.ActionType_kCreatePage:
		act = &createPage{
			boardAction: boardAction{baseAct, rawAction},
			CreatePage:  msg.(*v1.CreatePage),
		}
	case v1.ActionType_kFlipPage:
		act = &flipPage{
			boardAction: boardAction{baseAct, rawAction},
			FlipPage:    msg.(*v1.FlipPage),
		}
	case v1.ActionType_kDeletePage:
		act = &deletePage{
			boardAction: boardAction{baseAct, rawAction},
			DeletePage:  msg.(*v1.DeletePage),
		}
	case v1.ActionType_kChangePageBackground:
		act = &changePageBK{
			boardAction:          boardAction{baseAct, rawAction},
			ChangePageBackground: msg.(*v1.ChangePageBackground),
		}
	case v1.ActionType_kChangeBoardBackground:
		act = &changeBoardBK{
			boardAction:           boardAction{baseAct, rawAction},
			ChangeBoardBackground: msg.(*v1.ChangeBoardBackground),
		}
	case v1.ActionType_kSetActiveBoard:
		act = &setActiveBoard{
			boardAction:    boardAction{baseAct, rawAction},
			SetActiveBoard: msg.(*v1.SetActiveBoard),
		}
	case v1.ActionType_kCloseBoard:
		act = &closeBoard{
			boardAction: boardAction{baseAct, rawAction},
			CloseBoard:  msg.(*v1.CloseBoard),
		}
	case v1.ActionType_kZoomBoard:
		act = &zoomBoard{
			boardAction: boardAction{baseAct, rawAction},
			ZoomBoard:   msg.(*v1.ZoomBoard),
		}
	case v1.ActionType_kStepChange:
		act = &stepChange{
			boardAction:   boardAction{baseAct, rawAction},
			PptStepChange: msg.(*v1.PptStepChange),
		}
	case v1.ActionType_kPptMediaChange:
		act = &pptMediaChange{
			boardAction:    boardAction{baseAct, rawAction},
			PptMediaChange: msg.(*v1.PptMediaChange),
		}
	case v1.ActionType_kPptTriggerChange:
		act = &pptTriggerChange{
			boardAction:      boardAction{baseAct, rawAction},
			PptTriggerChange: msg.(*v1.PptTriggerChange),
		}

	default:
		return nil, biz_error.DecodeError.Wrap(errors.Errorf("no such board action %d", rawAction.Type))
	}
	log.V1.CtxInfo(ctx, "create action %s, %d", rawAction.Type, rawAction.Timestamp)
	return act, nil
}

type createPage struct {
	boardAction
	*v1.CreatePage
}

func (c *createPage) Execute(ctx context.Context) error {
	err := c.checkConflict(ctx)
	if err != nil {
		return err
	}

	if c.PrePageId == nil {
		return biz_error.InvalidParam.Wrap(errors.Errorf("prePageId not exists"))
	}
	boards, err := c.Repo.GetBoards(ctx, c, []int32{c.BoardId})
	if err != nil {
		return err
	}
	if len(boards) == 0 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("board %d not exists", c.BoardId))
	}

	row, err := c.Repo.CreatePages(ctx, c, c.Pages, *c.PrePageId)
	log.V1.CtxInfo(ctx, "created %d rows", row)
	return err
}

func (c *createPage) ConflictObjects() []string {
	return nil
}

type flipPage struct {
	boardAction
	*v1.FlipPage
}

func (f *flipPage) Execute(ctx context.Context) error {
	err := f.checkConflict(ctx)
	if err != nil {
		return err
	}
	pages, err := f.Repo.GetPages(ctx, f, []string{f.PageId})
	if err != nil {
		return err
	}
	if len(pages) == 0 {
		return biz_error.PacketReject.Wrap(errors.Errorf("page not exist"))
	}
	return f.Repo.FlipPage(ctx, f, f.PageId)
}
func (f *flipPage) ConflictObjects() []string {
	return nil
}

type deletePage struct {
	boardAction
	*v1.DeletePage
}

func (s *deletePage) Execute(ctx context.Context) error {
	err := s.checkConflict(ctx)
	if err != nil {
		return err
	}
	count, err := s.Repo.GetPageCount(ctx, s)
	if err != nil {
		return err
	}
	if count == 1 {
		return biz_error.PacketReject.Wrap(errors.Errorf("board just only one page"))
	}
	err = s.Repo.DeletePages(ctx, s, s.PageIds, s.IsContinuousPages)
	if err != nil {
		log.V1.CtxError(ctx, "DeletePages err:%+v ,pageIds:%+v", err, s.PageIds)
		return err
	}
	return nil

}
func (s *deletePage) ConflictObjects() []string {
	return nil
}

type changePageBK struct {
	boardAction
	*v1.ChangePageBackground
}

func (c *changePageBK) Execute(ctx context.Context) error {
	pages, err := c.Repo.GetPages(ctx, c, []string{c.GetPageId()})
	if err != nil {
		return err
	}
	if len(pages) == 0 {
		return biz_error.NotFound.Wrap(errors.Errorf("not find page :%s", c.GetPageId()))
	}
	page := pages[0]
	page.PageInfo.BkInfo = c.BkInfo
	return c.Repo.UpdatePage(ctx, c, page.ControlCount, 1, page.PageInfo)

}
func (c *changePageBK) ConflictObjects() []string {
	return nil
}

type changeBoardBK struct {
	boardAction
	*v1.ChangeBoardBackground
}

func (c *changeBoardBK) Execute(ctx context.Context) error {
	boards, err := c.Repo.GetBoards(ctx, c, []int32{c.GetBoardId()})
	if err != nil {
		return err
	}
	if len(boards) == 0 {
		return biz_error.NotFound.Wrap(errors.Errorf("not find board :%d", c.GetBoardId()))
	}
	board := boards[0]
	board.BkInfo = c.BkInfo
	return c.Repo.UpdateBoard(ctx, c, *board.BoardControlCount, 1, board)

}
func (c *changeBoardBK) ConflictObjects() []string {
	return nil
}

type setActiveBoard struct {
	boardAction
	*v1.SetActiveBoard
}

func (s *setActiveBoard) Execute(ctx context.Context) error {
	return s.Repo.UpdateActiveBoardId(ctx, s, s.GetActiveBoardId())

}
func (s *setActiveBoard) ConflictObjects() []string {
	return nil
}

type closeBoard struct {
	boardAction
	*v1.CloseBoard
}

func (c *closeBoard) Execute(ctx context.Context) error {
	_, err := c.Repo.CloseBoard(ctx, c, c.GetBoardId())
	return err

}
func (c *closeBoard) ConflictObjects() []string {
	return nil
}

type zoomBoard struct {
	boardAction
	*v1.ZoomBoard
}

func (z *zoomBoard) Execute(ctx context.Context) error {
	err := z.Repo.ZoomBoard(ctx, z, z.ZoomInfo)
	return err

}
func (z *zoomBoard) ConflictObjects() []string {
	return nil
}

type stepChange struct {
	boardAction
	*v1.PptStepChange
}

func (s *stepChange) Execute(ctx context.Context) error {
	err := s.boardAction.Action.Repo.StepChange(ctx, s, s.PptStepChange)
	return err

}
func (s *stepChange) ConflictObjects() []string {
	return nil
}

type pptMediaChange struct {
	boardAction
	*v1.PptMediaChange
}

func (p *pptMediaChange) Execute(ctx context.Context) error {
	err := p.boardAction.Action.Repo.PptMediaChange(ctx, p, p.PptMediaChange)
	return err
}
func (p *pptMediaChange) ConflictObjects() []string {
	return nil
}

type pptTriggerChange struct {
	boardAction
	*v1.PptTriggerChange
}

func (p *pptTriggerChange) Execute(ctx context.Context) error {
	err := p.boardAction.Action.Repo.PptTriggerChange(ctx, p, p.PptTriggerChange)
	return err
}
func (p *pptTriggerChange) ConflictObjects() []string {
	return nil
}
